"""
PyGame Mario
Seth Vanderwilt (with invaluable assistance from King Xia)
AP CS++
5/17/11

Work to do:
- add more types of enemies
- add support for more levels (level 2 not currently supported due to height problems)
- clean up code
"""

#### CURRENT PROJECT ####
# add FPS counter in corner
# make separate files for each class

###King's Notes
#Rather than more enemies, perhaps more class types,
#but those are mutable, or perhaps,
#the classes could just have latent functions

#Several unused functions commented out, to be deleted later on, possibly

###GLITCHES, VERY IMPORTANT!###
#Make Mario unable to 'hop up' blocks
#Don't let Mario jump when he's not on the ground (just after going off a block)

# Import necessary modules
import os
import pygame
import sys
import time
from sys import exit
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((1000, 448))
pygame.display.set_caption("Red Luigi [alpha]")
# unfortunately, pygame.display.set_mode can't come later
# we'll have to deal with the black window for now

# get the classes
from mario import *
from enemy import *


# Useful functions needed for the game
def image_load(name):
    image = pygame.image.load(name)
    image = image.convert()
    colorkey = image.get_at((0,0))
    image.set_colorkey(colorkey, RLEACCEL)
    return image

def collision(pos_x, pos_y, obj_x, obj_y):
    if (pos_x > obj_x - 32) and (pos_x < obj_x + 32) and (pos_y > obj_y - 32) and (pos_y < obj_y + 32):
        return True
    else:
        return False

#http://stackoverflow.com/questions/1927660/compare-two-images-the-python-linux-way
def compare(image1path, image2path):
    import Image
    import math
    import operator
    __docstring__ = "compare two image files"
    h1 = Image.open(image1path).histogram()
    h2 = Image.open(image2path).histogram()
    rms = math.sqrt(reduce(operator.add, map(lambda a,b: (a-b)**2, h1, h2))/len(h1))
    return rms == 0

def collide_edges(object_1, object_2):
	l, r, t, b = False, False, False, False
	Rect = pygame.Rect
	left = Rect(object_1.left, object_1.top+1, 1, object_1.height-1)
	right = Rect(object_1.right, object_1.top+1, 1, object_1.height-1)
	top = Rect(object_1.left + 1, object_1.top, object_1.width-1, 1)
	bottom = Rect(object_1.left + 1, object_1.bottom, object_1.width-1, 1)
	
	if left.colliderect(object_2):
		l = True
	if right.colliderect(object_2):
		r = True
	if top.colliderect(object_2):
		t = True
	if bottom.colliderect(object_2):
		b = True
	return (t,r,b,l)

def process_obstacle_coords(level_name):
    xyfile = open("obstacle_coords_file_" + level_name + ".txt")
    obstacle_list = []
    for line in xyfile:
        line = line.strip()
        line = line.split(",")
        for item in line:
            if item != "":
                item = float(item)
        obstacle_list.append(line)
    return obstacle_list

def process_enemy_coords(level_name):
    enemyfile = open("enemy_coords_file_" + level_name + ".txt")
    enemy_list = []
    for line in enemyfile:
        line = line.strip()
        line = line.split(",")
        enemy_list.append(line)
    return enemy_list

### Sprites
##class Enemy(pygame.sprite.Sprite):
##    def __init__(self,pos, enemy_type="Shyguy"): # pos = (x1, x2, y)
##        pygame.sprite.Sprite.__init__(self)
##        self.image = image_load(enemy_type + ".gif") # change filenames
##        self.rect = self.image.get_rect()
##        self.left_limit = int(pos[0])
##        self.right_limit = int(pos[1])
##        self.ypos = int(pos[2])
##        self.dx = 5        
##        self.rect.centerx = self.right_limit - 50
##        self.rect.bottom = int(self.ypos)
##        self.right_boundary = False
##        self.left_boundary = False
##    def to_write(self): # for adding enemy to file
##        return "%d,%d,%d" % (self.left_limit, self.right_limit, self.ypos)
##    def update(self):
##        self.rect.centerx += self.dx
##    def reset(self):
##        self.rect.centerx = self.right_limit - 50
##        self.rect.bottom = self.ypos
##        self.dx = 5

##class Obstacle(pygame.sprite.Sprite):
##    def __init__(self, pos):
##        pygame.sprite.Sprite.__init__(self)
##        self.defaults = pos
##        self.rect = Rect(int(self.defaults[0]), int(self.defaults[1]), int(32*float(self.defaults[2])), int(32*float(self.defaults[3])))
##        self.onGround = False
##    def update(self):
##        pass
##    def reset(self):
##        self.rect.top = int(self.defaults[1])
##        self.rect.left = int(self.defaults[0])

##class Mario(pygame.sprite.Sprite):
##    def __init__(self):
##        pygame.sprite.Sprite.__init__(self)
###        self.imagename = 'Mariostand.png'
##        self.imagename = 'small/Mariostand.png'
##        self.image = image_load(self.imagename)
##        self.rect = self.image.get_rect()
##        self.rect.centerx = 300
##        self.rect.bottom = 400
##        self.walkorstand = 0
##        self.jumping = False
##        self.jumptime = 0
##        self.Graceperiod = False
##        self.grounded = True
##        self.dead = False
##        self.screenpos = 0
##        self.movingright = False
##        self.movingleft = False
##        self.facing = "right"
##        self.dy = 0
##        self.ychange = 0
##        self.xchange = 0
##    def gravity(self):
##        return .02 * self.dy ** 2 - 1
##    def update(self):
##        self.xchange = 0
##        self.ychange = 0
##        key = pygame.key.get_pressed()
##        if key[K_UP]:
##            if not self.jumping:
##                self.jumping = True
##                self.jumptime = 17
##        if key[K_LEFT]:
##            self.movingleft = True
##            if self.rect.bottom <= 400:
##                #self.rect.centerx -= 10
##                self.xchange += -10
##            if self.facing != 'left':
##                self.image = pygame.transform.flip(self.image, 1, 0)
##                self.facing = 'left'
##            self.walkorstand += 1
##            if self.walkorstand >= 5:
##                if 'walk' in self.imagename:
##                    self.imagename = self.imagename.rstrip('walk.png')+'stand.png'
##                elif 'stand' in self.imagename:
##                    self.imagename = self.imagename.rstrip('stand.png')+'walk.png'
##                self.image = image_load(self.imagename)
##                self.image = pygame.transform.flip(self.image, 1, 0)
##                self.walkorstand = 0
##        if key[K_RIGHT]:
##            self.movingright = True
##            if self.rect.bottom <= 400:
##                #self.rect.centerx += 10
##                self.xchange += 10
##            self.walkorstand += 1
##            if self.facing != 'right':
##                self.image = pygame.transform.flip(self.image, 1, 0)
##                self.facing = 'right'
##            if self.walkorstand >= 5:
##                if 'walk' in self.imagename:
##                    self.imagename = self.imagename.rstrip('walk.png')+'stand.png'
##                elif 'stand' in self.imagename:
##                    self.imagename = self.imagename.rstrip('stand.png')+'walk.png'
##                self.image = image_load(self.imagename)
##                self.walkorstand = 0
##        if key[K_k]:
##            print("Hit Kill button @ (" + str(self.rect.left) + ", " + str(self.rect.top) + ")")
##            self.rect.top = 1000
##        if key[K_ESCAPE]:
##            exit()
##        keyempty = True
##        if key[K_UP] or key[K_LEFT] or key[K_RIGHT]:
##            keyempty = False
##        if keyempty:
##            if self.imagename.endswith('walk.png'):
##                self.imagename = self.imagename.rstrip('walk.png') + 'stand.png'
##            self.image = image_load(self.imagename)
##            if self.facing == 'left':
##                self.image = pygame.transform.flip(self.image, 1, 0)
##        if self.jumping:
##            self.Graceperiod = True
##            if self.jumptime > 0:
##                #self.rect.centery -= self.jumptime
##                self.ychange -= self.jumptime
##                self.jumptime -= 1
##            if self.jumptime <= 0:
##                self.jumptime = 0
##                if self.grounded:
##                    self.jumping = False
##                self.Graceperiod = False
##        # OK to let Mario jump above the top of the screen
##        #if self.rect.top < 0:
##            #self.rect.top = 0
##            #self.Graceperiod = False
##            #self.jumptime = 0
##        if self.rect.left < 0:
##            self.rect.left = 0
##        if self.rect.right > 1000:
##            self.rect.right = 1000
##       
##        self.onblock = False
##        for block in BLOCKS:
##            if block.rect.colliderect(self.rect):
##                self.onblock = True
##                top, right, bottom, left = collide_edges(block.rect, self.rect)
##                if top and not bottom:
##                    self.rect.bottom = block.rect.top
##                    self.grounded = True
##                    self.dy = 0
##                elif bottom and not top:
##                    self.rect.top = block.rect.bottom
##                    self.Graceperiod = False
##                    self.jumptime = 0
##                elif left:
##                    #self.rect.right = block.rect.left
##                    self.xchange = self.rect.right - block.rect.left
##                elif right:
##                    #self.rect.left = block.rect.right
##                    self.xchange = self.rect.left - block.rect.right
##        if not self.onblock:
##            self.grounded = False
##        if not self.grounded:
##            if not self.Graceperiod:
##                self.dy += 1
##                self.ychange += self.gravity()
##        self.rect.centerx += self.xchange
##        self.rect.centery += self.ychange
##    def reset(self):
##        self.screenpos = 0
##        self.rect.centerx = 300
##        self.rect.bottom = 400
##        self.grounded = True
##        self.xchange = 0
##        self.ychange = 0

##class Score(pygame.sprite.Sprite):
##    def __init__(self):
##        pygame.sprite.Sprite.__init__(self)
##        self.points = 10000
##        self.font = pygame.font.SysFont("None", 36, True, False)
##    def update(self):
##        self.text = "Score: " + str(self.points)
##        self.image = self.font.render(self.text, 1, (0,0,0))
##        self.rect = self.image.get_rect()
##    def reset(self):
##        self.points = 10000
##
##class FPS(pygame.sprite.Sprite):
##    def __init__(self):
##        pygame.sprite.Sprite.__init__(self)
##        self.fps = 0
##        self.font = pygame.font.SysFont("None", 36, True, False)
##    def update(self):
###        curtime = time.clock()
##        #print curtime
###        self.fps = 1.0 / (curtime - start_time)
##        self.fps = clock.get_fps()
##        self.text = str(self.fps) + " fps"
##        self.image = self.font.render(self.text, 1, (0, 0, 0))
##        self.rect = self.image.get_rect()
##        self.rect.left = 700
##    def reset(self):
##        self.fps = 0

##class HUD(pygame.sprite.Sprite):
##    def __init__(self):
##        pygame.sprite.Sprite.__init__(self)
##        self.points = 10000
##        self.fps = 0
##        self.font = pygame.font.SysFont("None", 36, True, False)
##    def update(self):
##        self.text = "Score: " + str(self.points) + "     " + str(self.fps) + " fps"
##        self.image = self.font.render(self.text, 1, (0, 0, 0))
##        self.rect = self.image.get_rect()
##    def reset(self):
##        self.points = 10000
##        self.fps = 0
        

def main():
    deaths = 0
    # Load all enemies, blocks and background images
    level_name = raw_input("Select Level: ")
    user_name = raw_input("User Name: ")
    background = pygame.image.load("mario-1-" + level_name + ".gif")
    # Make a list of all the obstacles
    all_obstacles = process_obstacle_coords(level_name)
    # Must be global or the mario class can't reference it
    global BLOCKS
    BLOCKS = []
    for obstacle in all_obstacles:
        x = obstacle[0]
        y = obstacle[1]
        width = obstacle[2]
        height = obstacle[3]
        #print x,y,width,height
        BLOCKS.append(Obstacle([x,y,width, height]))
    # Make a list of all enemies
    all_enemies = process_enemy_coords(level_name)
    ENEMIES = []
    for enemy in all_enemies:
        ENEMIES.append(Enemy([enemy[0], enemy[1], enemy[2]]))
    # Load high-scores file
    win_file = open("win_file_" + level_name + ".txt", "a")
    # Initialize sprites and the screen caption
    #pygame.display.set_caption('Red Luigi [alpha]')
    mario = Mario()
##    score = Score()
##    fps = FPS()
    hud = HUD()
#    SpriteGroup = pygame.sprite.Group(mario, ENEMIES, score, fps)
    SpriteGroup = pygame.sprite.Group(mario, ENEMIES, hud)

    screen.blit(background, (mario.screenpos,0))
    global clock
    clock = pygame.time.Clock()
    keepGoing = True
    print("Starting Game in 3 Seconds...\n\tmake sure the PyGame window is active!")
    time.sleep(3)    # Allows time to get ready to play
    while keepGoing:
##        global start_time
##        start_time = time.clock() # for FPS
        #print start_time
        clock.tick(30)
#        score.points -= 2
        hud.points -= 2
        # Check for clicking or quit events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                keepGoing = False
            elif event.type == MOUSEBUTTONDOWN:
                mousecoord = pygame.mouse.get_pos()
                x = mousecoord[0] - mario.screenpos
                y = mousecoord[1]
                print(str(x) + ", " + str(y))
            key = pygame.key.get_pressed()
            if key[K_a]:
                mousecoord = pygame.mouse.get_pos()
                x = mousecoord[0] - mario.screenpos
                y = mousecoord[1]
                enemyfile = open("enemy_coords_file_" + level_name + ".txt", 'r+')
                new_enemy = Enemy((x, x+200, y))
                ENEMIES.append(new_enemy)
                out = new_enemy.to_write() # not working for some reason
                enemyfile.write(out)
                print new_enemy.to_write()
        SpriteGroup.clear(screen, background)
        # Used for sidescrolling, moves blocks and enemies
        if mario.movingright and mario.rect.right > 700:
            mario.rect.right = 700
            mario.screenpos -= 10
            for block in BLOCKS:
                block.rect.centerx -= 10
            for enemy in ENEMIES:
                enemy.rect.centerx -= 10
        if mario.movingleft and mario.rect.left < 300 and mario.screenpos <= -10:
            mario.rect.left = 300
            mario.screenpos += 10
            for block in BLOCKS:
                block.rect.centerx += 10
            for enemy in ENEMIES:
                enemy.rect.centerx += 10
        # Update the enemy sprite images
        for enemy in ENEMIES:
            if enemy.rect.left < enemy.left_limit + mario.screenpos:
                enemy.dx *= -1
                enemy.image = image_load('Shyguy.gif')
            if enemy.rect.right > enemy.right_limit + mario.screenpos:
                enemy.dx *= -1
                enemy.image = image_load('Shyguyleft.gif')
        
        # Check for collision with enemies or falling off the screen
        for enemy in ENEMIES:
            if collision(mario.rect.centerx, mario.rect.centery, enemy.rect.centerx, enemy.rect.centery):
                deaths += 1
                print("Dead...\n\tYou've died " + str(deaths) + " times.")
                time.sleep(1.5)
                mario.reset()
#                score.reset()
                hud.reset()
                for block in BLOCKS:
                    block.reset()
                for enemy in ENEMIES:
                    enemy.reset()
        if mario.rect.bottom >= 448:
            deaths += 1
            print("You fell down...")
            time.sleep(1.5)
            mario.reset()
#            score.reset()
            hud.reset()
            for block in BLOCKS:
                block.reset()
            for enemy in ENEMIES:
                enemy.reset()
        # Have you beaten the level?
        if mario.screenpos <= -5650:
            print("You beat the level\n\tResetting in 3 seconds or press Esc to quit or try the next level\n\nYour score was " + str(hud.points) + ".")
            win_time = time.localtime()
            year = win_time[0]
            month = win_time[1]
            day = win_time[2]
            hour = win_time[3]
            minute = win_time[4]
            if month <= 9:
                month = "0" + str(month)
            win_file.write("User: " + user_name + " Score: " + str(hud.points) + " Time: " + str(year) + "-" + str(month) + "-" + str(day) + " " + str(hour) + ":" + str(minute) + "\n")
            time.sleep(3)
            mario.reset()
            hud.reset()
#            score.reset()
            for block in BLOCKS:
                block.reset()
            for enemy in ENEMIES:
                enemy.reset()
        # Update the display
        screen.blit(background, (mario.screenpos, 0))
        SpriteGroup.update()
        #fps.update(start_time)
        SpriteGroup.draw(screen)
        #fps.draw(screen)
        pygame.display.flip()
                
# Runs the game soundtrack
pygame.mixer.init(22050, -16, 2, 4096)
pygame.mixer.music.load('Super Mario Brothers - Overworld Remix.mp3')
pygame.mixer.music.play(-1)

# Starts the game
if __name__ == "__main__":
    main()
